#!/usr/bin/perl
###############################################################################
# vim:sw=4:
#
# gdb integration for vim
#
# Authors: Thomas Zellerin
#          TS Urban
#          Markus Kremer <Markus.Kremer@gmx.de>
#
# Features:
# - autostart vim
# - autocreate pipe
# - input from pipe and tty
# - command history
# - easy keymappings
# - works from any directory
#
###############################################################################
#
# todo:
#
# - any suggestions ?
#
#
###############################################################################
#
# History:
#
# 09/05/01 Markus  seems to work with pipes, history and ctrl c
#
#

use POSIX qw(tmpnam);
use Term::ReadLine; # used in input_tty
use English;
use Getopt::Long;
use Config;

$VIMNAME="GDBVIM"; # name of vim server
$PIPE=tmpnam(); # name of communication pipe, chmod 600

# atexit handler
END { { unlink ($PIPE) or die "unlink $PIPE: $!"; }}

# process arguments
$result = GetOptions (
        'server=s'              => \$server,
        'help!'                 => \$help
);

# print @ARGV, "\n";

## unknown option or help asked for
if (!$result || $help) # {{{
{
        help ();
        exit 1;
} # }}}

$VIMNAME=$server if(defined($server));

$runnings=`vim -u NONE -U NONE --serverlist`; # all running vim servers
if(not ($runnings=~/^$VIMNAME$/m)) # test if server is not already there
{
    # start gvim server
    $result = system ("gvim --servername $VIMNAME");
    if ($result != 0)
    {
        die "error starting gvim server: $!";
    }
};

# create fifo - some systems have mkfifo and some mknod
if (system ("mkfifo -m 600 $PIPE") && system ("mknod -m 600 $PIPE p"))
{
    die "couldn't create fifo: $!";
}



pipe(WORKER_RDR, GDB_WTR);  # setup pipes for communication
pipe(GDB_RDR, WORKER_WTR); #
pipe(RL_RDR, RL_WTR); #

select(GDB_WTR);    $OUTPUT_AUTOFLUSH = 1; # make unbuffered
select(WORKER_WTR); $OUTPUT_AUTOFLUSH = 1; # make unbuffered
select(RL_WTR);     $OUTPUT_AUTOFLUSH = 1; # make unbuffered
select(STDOUT);     $OUTPUT_AUTOFLUSH = 1; # make unbuffered

$SIG{INT}=\&sig_gdb; # disable CTRL-C

###############################################################################
#
# create processes
#
###############################################################################

die "cannot fork" unless(defined($gdb_pid=fork)); # is there a better way for this line?

if($gdb_pid==0) # {{{
{
    close GDB_RDR; close GDB_WTR; # gdb does not need them
    open(STDOUT, ">&WORKER_WTR") || die "Can't redirect stdout";
    open(STDIN, "<&WORKER_RDR")  || die "Can't redirect stdin";
    gdb();
} # }}}

close WORKER_RDR; close WORKER_WTR; # we do not need them any more


die "cannot fork" unless(defined( $input_tty_pid=fork ));
input_tty() if ($input_tty_pid==0);
die "cannot fork" unless(defined( $input_pipe_pid=fork ));
input_pipe() if ($input_pipe_pid==0);

$worker_pid=$PID;

$line = "";
if (open($SOURCE, "< .gdbvim_breakpoints"))
{
    while (<$SOURCE> && $line == "")
    {
        $line = readline($SOURCE);
    }
}

vim_call('Gdb_interf_init(\"'.$PIPE.'\", \"'.$ENV{"PWD"}.'\")');
print GDB_WTR "set prompt (gdb)\\n\n";

foreach $item(split(/\|/, $line))
{
    @token = split(/:/, $item);
    print "Gdb_togglebreak(\\\"@token[0]\\\", @token[1])";
    vim_call("Gdb_togglebreak(\\\"@token[0]\\\", @token[1])");
    print "\n";
}

worker();

kill(15, $gdb_pid,$input_pipe_pid,$input_tty_pid);
while(wait()!=-1) # wait for dying children
{
   ;
};
exit;

###############################################################################
#
# functions
#
###############################################################################

# this function reads from stdin and outputs to gdb
sub input_tty # {{{
{
$term=new Term::ReadLine 'GDBVIM';
    while(<RL_RDR>)
    {
        if(defined($_=$term->readline("")))
        {
            print GDB_WTR "$_\n";
        };
    };
}; # }}}

# this function reads from a pipe and outputs to gdb
sub input_pipe # {{{
{
    while(1)
    {
        open(IN,"<$PIPE") or die "opening $PIPE failed";
        while(<IN>)
        {
            print "$_"; # As from input
            print GDB_WTR $_;
        };
        close IN;
    }
    die "should have never been reached !!!";
}; # }}}

# this function processes the gdb output, prints it and sends vim commands
sub worker # {{{
{
    while (<GDB_RDR>){
        if (/^\(gdb\)/)
        {
            print RL_WTR "READ\n";
            print "(gdbvim) ";
        }
        elsif (
                s/Breakpoint ([0-9]+) at 0x.*: file ([^,]+), line ([0-9]+)./Gdb_Bpt($1,\\\"$2\\\",$3)/ ||
                s/\032\032([^:]*):([0-9]+).*/Gdb_CurrFileLine(\\\"$1\\\", $2)/
              )
        {
            chomp;
            vim_call($_);
            print RL_WTR;
        }
        elsif (
                s/Deleted breakpoints ([ 0-9]+)/$1/
              )
        {
            chomp;
            foreach $item(split(/ /))
            {
                vim_call("Gdb_NoBpt($item)");
            }
            print RL_WTR;
        }
        elsif (
                s/Deleted breakpoint ([0-9]+)/Gdb_NoBpt($1)/
              )
        {
            chomp;
            vim_call($_);
            print RL_WTR;
        }
        else
        {
            print ;
        }
    }
    vim_call("Gdb_interf_close()");
    close GDB_RDR; close GDB_WTR;
}; # }}}

# this function execs gdb with the supplied parameters
sub gdb
{
    exec("gdb","-f",@ARGV) or die "exec failed";
};

# this function calls a remote vim function
sub vim_call
{
    my $arg=shift||return;
#    print "Sending $arg";
    system("vim --servername $VIMNAME -u NONE -U NONE --remote-send \"<C-\\\\><C-N>:call $arg<CR>\"");
};

# send a CTRL_C to the debugged programm
sub sig_gdb # {{{
{
    my $child_pid;
    return unless(defined $worker_pid); #
    return unless($PID eq $worker_pid); # only worker will send the signal
    $child_pid=`ps -e -o pid,ppid|grep $gdb_pid\$|cut -c 1-6`;
    print "interrupting $child_pid\n";

    system("kill -SIGINT ".$child_pid);
}; # }}}

# print help information
sub help # {{{
{
    print <<END;
$0 [--server=NAME] [gdb options ...]
END
exit 1;
}; # }}}
# vim: set foldmethod=marker
